﻿using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace parts
{
    public class Part
    {
        protected static readonly IDictionary<string, Func<object, object >> methodContainer = new Dictionary<string, Func<object, object>>();

        public static void Add<TInput, TOuput>(string partName, Func<TInput, TOuput> part) {
            methodContainer.Add(partName, x => part((TInput)x));
        }

        public static void Add<TInput, TOutput>(string partName, IPart<TInput, TOutput> implementation) {
            methodContainer.Add(partName, x => {
                TOutput result = default(TOutput);
                implementation.Result += y => result = y;
                implementation.Process((TInput)x);
                return result;
            });
        }

        public static void Cleanup() {
            methodContainer.Clear();
        }
    }

    public class Part<TInput, TOutput> : Part
    {
        private readonly string partName;
        private Func<TInput, TOutput> method;
        private bool resolved;

        public Part(Func<TInput, TOutput> method) {
            partName = method.Method.Name;
            this.method = method;
            resolved = true;
        }

        public Part(string partName) {
            this.partName = partName;
            method = x => default(TOutput);
            resolved = false;
        }

        public void Process(TInput input) {
            Trace.TraceInformation("{0}.Process", partName);
            if (!resolved && methodContainer.ContainsKey(partName)) {
                var f = methodContainer[partName];
                method = x => (TOutput)f(x);
                resolved = true;
            }
            Result(method(input));
        }

        public event Action<TOutput> Result;
    }

    public class Part<TInput, TOutput, TImpl> where TImpl : IPart<TInput, TOutput>, new()
    {
        private readonly Action<TInput> method;

        public Part() {
            var part = new TImpl();
            this.method = x => part.Process(x);
            part.Result += m => Result(m);
        }

        public void Process(TInput input) {
            method(input);
        }

        public event Action<TOutput> Result;
    }
}